﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.Configuration;
using System.Reflection;
using System.Diagnostics;
using Microsoft.Win32;

namespace PSE.Framework.Configuration
{
    /// <summary>
    /// Classe singleton responsável por carregar o arquivo de configuração global da aplicação (AppSettings.config), contido
    /// no diretório raíz da aplicação
    /// </summary>
    public class ApplicationConfiguration : ConfigurationSectionGroup
    {
        private static SystemComponent _system;
        private static SystemComponent _component;
        private static System.Configuration.Configuration _config;
        private static readonly string MutexId = "Global\\{CA54C11A-0296-4285-9F8D-8ADF1AA86313}";

        /// <summary>
        /// Sistema corrente
        /// </summary>
        public static SystemComponent CurrentSystem
        {
            get
            {
                CheckIsInitialized();

                return _system;
            }
        }

        /// <summary>
        /// Componente do sistema corrente
        /// </summary>
        public static SystemComponent CurrentComponent
        {
            get
            {
                CheckIsInitialized();

                return _component;
            }
        }

        private static bool _isInitialized = false;
        /// <summary>
        /// Indica se o application configuration já foi inicializado com o sistema e componente correntes
        /// </summary>
        public static bool IsInitialized
        {
            get { return _isInitialized; }
        }

        #region Public Methods

        /// <summary>
        /// Inicializa o application configuration com o sistema e componente correntes
        /// </summary>
        /// <param name="system">Sistema corrente</param>
        /// <param name="component">Componente do sistema corrente</param>
        public static void Initialize(SystemComponent system, SystemComponent component)
        {
            _system = system;
            _component = component;
            _isInitialized = true;
        }

        /// <summary>
        /// Captura a configuração do sistema corrente
        /// </summary>
        /// <returns></returns>
        public static ApplicationConfiguration GetCurrent()
        {
            CheckIsInitialized();

            return GetCurrent(_system);
        }

        /// <summary>
        /// Captura a configuração do sistema informado
        /// </summary>
        /// <param name="system">Sistema</param>
        /// <returns>Configuração do sistema</returns>
        public static ApplicationConfiguration GetCurrent(SystemComponent system)
        {
            return GetCurrent(GetApplicationRootDirectory(system));
        }

        /// <summary>
        /// Captura a configuração do sistema na pasta informada
        /// </summary>
        /// <param name="configurationDirectory">Pasta raiz do sistema</param>
        /// <returns>Configuração do sistema</returns>
        public static ApplicationConfiguration GetCurrent(string configurationDirectory)
        {
            ApplicationConfiguration instance;

            using (Mutex mutex = new Mutex(false, MutexId))
            {
                try
                {
                    mutex.WaitOne();

                    //carregar o arquivo de configuracao
                    _config = LoadApplicationConfiguration(configurationDirectory);

                    //criar a instance que contem as configuracoes
                    instance = (ApplicationConfiguration)_config.GetSectionGroup("applicationConfiguration");
                }
                catch
                {
                    throw;
                }
                finally
                {
                    mutex.ReleaseMutex();
                }
            }

            return instance;
        }

        /// <summary>
        /// Captura a pasta raiz do sistema corrente
        /// </summary>
        /// <returns>Pasta raiz do sistema</returns>
        public static string GetApplicationRootDirectory()
        {
            CheckIsInitialized();

            return GetApplicationRootDirectory(_system);
        }

        /// <summary>
        /// Captura a pasta raiz do sistema informado
        /// </summary>
        /// <param name="system">Sistema</param>
        /// <returns>Pasta raiz do sistema</returns>
        public static string GetApplicationRootDirectory(SystemComponent system)
        {
            RegistryKey key = GetApplicationRegistryKey(system.Name, true);

            string applicationRootDirectory = Convert.ToString(key.GetValue("ApplicationRootDirectory"));

            if (string.IsNullOrEmpty(applicationRootDirectory))
                throw new Exception(string.Format("Cannot find ApplicationRootDirectory in registry key '{0}'", string.Format(@"{0}\SOFTWARE\{1}", Registry.LocalMachine.ToString(), system.Name)));

            if (!Directory.Exists(applicationRootDirectory))
               throw new DirectoryNotFoundException(string.Format("Application root directory '{0}' not found", applicationRootDirectory));

            return applicationRootDirectory;
        }

        /// <summary>
        /// Captura uma ConfigurationSection do arquivo de configuração
        /// </summary>
        /// <typeparam name="T">Type da ConfigurationSection</typeparam>
        /// <param name="sectionName">Nome da ConfigurationSection</param>
        /// <returns>ConfigurationSection</returns>
        public T GetSection<T>(string sectionName)
            where T : ConfigurationSection
        {
            T ret = null;
            ret = (T)this.Sections[sectionName];

            if (ret == null)
            {
                //tentar buscar de forma case insensitive
                foreach (ConfigurationSection section in this.Sections)
                {
                    if (section.SectionInformation.SectionName.ToUpper() == sectionName.ToUpper())
                    {
                        ret = (T)section;
                        break;
                    }
                }
            }

            if (ret == null)
                throw new ConfigurationErrorsException(string.Format("The section '{0}' doesn't exist in the configuration file.", sectionName));

            return ret;
        }

        /// <summary>
        /// Salva o arquivo de configuração
        /// </summary>
        public void Save()
        {
            if (_config != null)
            {
                using (Mutex mutex = new Mutex(false, MutexId))
                {
                    try
                    {
                        mutex.WaitOne();

                        _config.Save();
                    }
                    catch
                    {
                        throw;
                    }
                    finally
                    {
                        mutex.ReleaseMutex();
                    }
                }
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Carregar as configurações da aplicação
        /// </summary>
        private static System.Configuration.Configuration LoadApplicationConfiguration(string baseDir)
        {
            //_config = System.Configuration.ConfigurationManager.OpenExeConfiguration(GetApplicationConfig().FullName);
            ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();

            FileInfo fileInfo = GetApplicationConfig(baseDir);

            if (fileInfo != null)
            {
                fileMap.ExeConfigFilename = fileInfo.FullName;
                return ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
            }
            else
            {
                throw new System.IO.FileNotFoundException("Config file 'GeneralSettings.config' not found");
            }
        }

        private static FileInfo GetApplicationConfig(string baseDir)
        {
            DirectoryInfo dir = new DirectoryInfo(baseDir);
            return GetApplicationConfig(dir);
        }

        private static FileInfo GetApplicationConfig(DirectoryInfo directory)
        {
            if (directory != null)
            {
                //procurar pelo arquivo de configuração da aplicação
                FileInfo[] files = directory.GetFiles("GeneralSettings.config");
                if (files.Length == 1)
                {
                    return files[0];
                }
                else
                    return GetApplicationConfig(directory.Parent);
            }
            else
            {
                return null;
            }
        }

        private static void CheckIsInitialized()
        {
            if (!_isInitialized)
                throw new Exception("The application configuration is not initialized");
        }

        internal static RegistryKey GetApplicationRegistryKey(string systemName, bool throwError)
        {
            RegistryKey key = Registry.LocalMachine.OpenSubKey(string.Format(@"SOFTWARE\{0}", systemName));

            if (throwError && key == null)
                throw new Exception(string.Format("Cannot find registry key '{0}'", string.Format(@"{0}\SOFTWARE\{1}", Registry.LocalMachine.ToString(), systemName)));

            return key;
        }

        #endregion
    }
}